/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Jul 9, 2008
 */

package com.bigdata.relation.locator;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

import junit.framework.TestCase2;

import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IPredicate;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.journal.BufferMode;
import com.bigdata.journal.IIndexManager;
import com.bigdata.journal.ITx;
import com.bigdata.journal.Journal;
import com.bigdata.journal.Journal.Options;
import com.bigdata.rdf.spo.ISPO;
import com.bigdata.relation.AbstractRelation;
import com.bigdata.relation.AbstractResource;
import com.bigdata.service.IBigdataFederation;
import com.bigdata.striterator.IChunkedOrderedIterator;
import com.bigdata.striterator.IKeyOrder;
import com.bigdata.util.NT;

/**
 * Test suite for location relations, etc.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 * 
 * @todo make this a proxy test case and run for {@link Journal} and
 *       {@link IBigdataFederation}
 */
public class TestDefaultResourceLocator extends TestCase2 {

    public TestDefaultResourceLocator() {
        super();
    }
    
    public TestDefaultResourceLocator(String name) {
        super(name);
    }

    public Properties getProperties() {
        
        Properties properties = super.getProperties();
        
        properties.setProperty(Options.BUFFER_MODE, BufferMode.Transient
                .toString());
        
        return properties;
        
    }
    
    /**
     * @todo locate a relation.
     * 
     * @todo locate a relation, write on it, verify read-committed view not yet
     *       visible, then commit and verify the read committed view is visible.
     * 
     * @todo same as above, but the relation is on a temporary store that is
     *       added to the set of index managers against which relations are
     *       resolved.
     * 
     */
    public void test_locateRelation() {
        
        final Properties properties = getProperties();
        
        final Journal store = new Journal( properties );

        final String namespace = "test";
        
        try {

            // instantiate relation.
            MockRelation mockRelation = new MockRelation(store, namespace,
                    ITx.UNISOLATED, properties);
            
            /*
             * the index for the relation does not exist yet. we verify
             * that, and then create the index.
             */

            assertNull(mockRelation.getIndex());

            // create indices.
            mockRelation.create();

            assertNotNull(mockRelation.getIndex());

            {
                /*
                 * Should be locatable now since the writes on the global row
                 * store are unisolated (ah!, but only if you are using the
                 * concurrency control API)
                 */
                assertNotNull(store.getResourceLocator().locate(namespace,
                        ITx.UNISOLATED));

                // a request for the unisolated view gives us the same instance.
                assertTrue(((MockRelation) store.getResourceLocator().locate(
                        namespace, ITx.UNISOLATED)) == mockRelation);

                /*
                 * the read-committed relation is also locatable since its in
                 * the global row store but it will not see the indices until
                 * (a) they have been created; and (b) there has been a commit
                 * of the journal.
                 */
                assertNotNull(store.getResourceLocator().locate(namespace,
                        ITx.READ_COMMITTED));

                // a request for the read committed view is not the same
                // instance as the unisolated view.
                assertTrue(((MockRelation) store.getResourceLocator().locate(
                        namespace, ITx.READ_COMMITTED)) != mockRelation);

            }
            
            {

                // a request for the unisolated view shows that the index
                // exists.
                assertNotNull(((MockRelation) store.getResourceLocator()
                        .locate(namespace, ITx.UNISOLATED)).getIndex());

                // a request for the unisolated view gives us the same instance.
                assertTrue(((MockRelation) store.getResourceLocator().locate(
                        namespace, ITx.UNISOLATED)) == mockRelation);

                /*
                 * @todo The read-committed view still does not see the relation
                 * since there has not been a commit yet after the index was
                 * created.
                 */
                if(false) {

                assertNull(((MockRelation) store.getResourceLocator().locate(
                        namespace, ITx.READ_COMMITTED)));
            
                final MockRelation readCommittedView1 = (MockRelation) store
                        .getResourceLocator().locate(namespace,
                                ITx.READ_COMMITTED);

                // same view before a commit.
                assertTrue(readCommittedView1 == (MockRelation) store
                        .getResourceLocator().locate(namespace,
                                ITx.READ_COMMITTED));

                
                // commit
                store.commit();

                /*
                 * should be a new read-committed view
                 * 
                 * FIXME cache must be defeated for read-committed!!! at least
                 * if there HAS been a commit
                 */
                final MockRelation readCommittedView2 = (MockRelation) store
                        .getResourceLocator().locate(namespace,
                                ITx.READ_COMMITTED);
        
                // different view after a commit.
                assertTrue(readCommittedView1 != readCommittedView2);
                
                /*
                 * The index is now visible to the read committed view.
                 */
                assertNull(readCommittedView2.getIndex());
                
                // still not visible to the old view.
                assertNull(readCommittedView1.getIndex());
                
                // another request gives us the same view
                assertTrue(readCommittedView2 == (MockRelation) store
                        .getResourceLocator().locate(namespace,
                                ITx.READ_COMMITTED));
                
                }

            }
            
        } finally {
            
            store.destroy();
            
        }
        
    }

    /**
     * Unit test for property caching for locatable resources.
     */
    public void test_propertyCache() {
        
        final Properties properties = getProperties();
        
        final Journal store = new Journal( properties );

        final String namespace = "test";
        
        try {

            // write a small record onto the journal and force a commit.
            {
                final ByteBuffer b = ByteBuffer.allocate(4);
                b.putInt(0);
                b.flip();
                store.write(b);
                assertNotSame(0L,store.commit());
            }
            
            // verify resource can not be located yet.
            {
                // resource does not exist in UNISOLATED view.
                assertNull(store.getResourceLocator().locate(namespace,
                        ITx.UNISOLATED));

                // resource does not exist at lastCommitTime.
                assertNull(store.getResourceLocator().locate(namespace,
                        store.getLastCommitTime()));

                // resource does not exist at read-only tx.
                {
                    final long tx = store.newTx(ITx.READ_COMMITTED);
                    try {
                        assertNull(store.getResourceLocator().locate(namespace,
                                store.getLastCommitTime()));
                    } finally {
                        store.abort(tx);
                    }
                }
            }

            // instantiate relation.
            MockRelation mockRelation = new MockRelation(store, namespace,
                    ITx.UNISOLATED, properties);

            // verify resource still can not be located.
            {
                // resource does not exist in UNISOLATED view.
                assertNull(store.getResourceLocator().locate(namespace,
                        ITx.UNISOLATED));

                // resource does not exist at lastCommitTime.
                assertNull(store.getResourceLocator().locate(namespace,
                        store.getLastCommitTime()));

                // resource does not exist at read-only tx.
                {
                    final long tx = store.newTx(ITx.READ_COMMITTED);
                    try {
                        assertNull(store.getResourceLocator().locate(namespace,
                                store.getLastCommitTime()));
                    } finally {
                        store.abort(tx);
                    }
                }
            }
            
            // create the resource, which writes the properties into the GRS.
            mockRelation.create();

            /*
             */
            {

                /*
                 * The UNISOLATED view of the resource should be locatable now
                 * since the writes on the global row store are unisolated.
                 */
                assertNotNull(store.getResourceLocator().locate(namespace,
                        ITx.UNISOLATED));

                // a request for the unisolated view gives us the same instance.
                assertTrue(store.getResourceLocator().locate(namespace,
                        ITx.UNISOLATED) == mockRelation);

                /*
                 * Note: The read-committed view is not, in fact, locatable.
                 * This is because the GRS update on the Journal does not
                 * include a commit.  That update will become visible only
                 * once we do a Journal.commit().
                 */
                assertNull(store.getResourceLocator().locate(namespace,
                        ITx.READ_COMMITTED));

//                /*
//                 * The read-committed view of the resource is also locatable.
//                 */
//                assertNotNull(store.getResourceLocator().locate(namespace,
//                        ITx.READ_COMMITTED));
//
//                /*
//                 * The read committed view is not the same instance as the
//                 * unisolated view.
//                 */
//                assertTrue(((MockRelation) store.getResourceLocator().locate(
//                        namespace, ITx.READ_COMMITTED)) != mockRelation);

            }

            // commit time immediately proceeding this commit.
            final long priorCommitTime = store.getLastCommitTime();
            
            // commit, noting the commit time.
            final long lastCommitTime = store.commit();

            {
                /*
                 * The read-committed view of the resource is also locatable.
                 */
                assertNotNull(store.getResourceLocator().locate(namespace,
                        ITx.READ_COMMITTED));

                /*
                 * The read committed view is not the same instance as the
                 * unisolated view.
                 */
                assertTrue(((MockRelation) store.getResourceLocator().locate(
                        namespace, ITx.READ_COMMITTED)) != mockRelation);

            }

            if(log.isInfoEnabled()) {
                log.info("priorCommitTime=" + priorCommitTime);
                log.info("lastCommitTime =" + lastCommitTime);
            }
            
            /*
             * Now create a few transactions against the newly created resource
             * and verify that the views returned for those transactions are
             * distinct, but that they share the same set of default Properties
             * (e.g., the propertyCache is working).
             * 
             * @todo also test a read-historical read !
             */
            final long tx1 = store.newTx(store.getLastCommitTime()); // read-only tx
            final long tx2 = store.newTx(store.getLastCommitTime()); // read-only tx
            final long ts1 = store.getLastCommitTime() - 1; // historical read
            try {

                assertTrue(tx1 != tx2);
                assertTrue(ts1 != tx1);
                assertTrue(ts1 != tx2);

                /*
                 * @todo There might not be enough commit latency to have
                 * lastCommitTime - 1 be GT priorCommitTime. If this happens
                 * either resolve issue 145 or add some latency into the test.
                 * 
                 * @see http://sourceforge.net/apps/trac/bigdata/ticket/145
                 */
                assertTrue(ts1 > priorCommitTime);

                // unisolated view.
                final AbstractResource<?> view_un = (AbstractResource<?>) store
                        .getResourceLocator().locate(namespace, ITx.UNISOLATED);
                assertNotNull(view_un);

                // tx1 view.
                final AbstractResource<?> view_tx1 = (AbstractResource<?>) store
                        .getResourceLocator().locate(namespace, tx1);
                assertNotNull(view_tx1);

                // tx2 view.
                final AbstractResource<?> view_tx2 = (AbstractResource<?>) store
                        .getResourceLocator().locate(namespace, tx2);
                assertNotNull(view_tx2);

                // all views are distinct.
                assertTrue(view_un != view_tx1);
                assertTrue(view_un != view_tx2);
                assertTrue(view_tx1 != view_tx2);

                // each view has its correct timestamp.
                assertEquals(ITx.UNISOLATED, view_un.getTimestamp());
                assertEquals(tx1, view_tx1.getTimestamp());
                assertEquals(tx2, view_tx2.getTimestamp());

                /*
                 * Read-only views report the commit time from which they were
                 * materialized.
                 */
                assertEquals(null, ((MockRelation) view_un).getCommitTime());
                assertEquals(Long.valueOf(lastCommitTime),
                        ((MockRelation) view_tx1).getCommitTime());
                assertEquals(Long.valueOf(lastCommitTime),
                        ((MockRelation) view_tx2).getCommitTime());

                // each view has its own Properties object.
                final Properties p_un = view_un.getProperties();
                final Properties p_tx1 = view_tx1.getProperties();
                final Properties p_tx2 = view_tx2.getProperties();
                assertTrue(p_un != p_tx1);
                assertTrue(p_un != p_tx2);
                assertTrue(p_tx1 != p_tx2);

                /*
                 * Verify that the [propertyCache] is working.
                 * 
                 * Note: Unfortunately, I have not been able to devise any means
                 * of testing the [propertyCache] without exposing that as a
                 * package private object.
                 */
                final DefaultResourceLocator<?> locator = (DefaultResourceLocator<?>) store
                        .getResourceLocator();

                // Not cached for the UNISOLATED view (mutable views can not be
                // cached).
                assertNull(locator.propertyCache.get(new NT(namespace,
                        ITx.UNISOLATED)));

//                if (true) {
//                    final Iterator<Map.Entry<NT, WeakReference<Map<String, Object>>>> itr = locator.propertyCache
//                            .entryIterator();
//                    while (itr.hasNext()) {
//                        final Map.Entry<NT, WeakReference<Map<String, Object>>> e = itr
//                                .next();
//                        System.err.println(e.getKey() + " => "
//                                + e.getValue().get());
//                    }
//                }

                // Not cached for the actual tx ids or read-only timestamp.
                assertNull(locator.propertyCache.get(new NT(namespace,tx1)));
                assertNull(locator.propertyCache.get(new NT(namespace,tx2)));
                assertNull(locator.propertyCache.get(new NT(namespace,ts1)));

                /*
                 * Cached for the last commit time, which should have been used
                 * to hand back the Properties for {tx1, tx2, ts1}.
                 */
                assertNotNull(locator.propertyCache.get(new NT(namespace,
                        lastCommitTime)));
                
                // nothing for the prior commit time.
                assertNull(locator.propertyCache.get(new NT(namespace,
                        priorCommitTime)));

            } finally {
                store.abort(tx1);
                store.abort(tx2);
            }
            
        } finally {
            
            store.destroy();
            
        }

    }
    
    @SuppressWarnings("unchecked")
    private static class MockRelation extends AbstractRelation {

        static final private String indexName = "foo";
        
        private IIndex ndx;
        
        /**
         * Exposed to the unit tests.
         */
        @Override
        public Long getCommitTime() {
            return super.getCommitTime();
        }
        
        /**
         * @param indexManager
         * @param namespace
         * @param timestamp
         * @param properties
         */
        public MockRelation(IIndexManager indexManager, String namespace, Long timestamp,
                Properties properties) {
            
            super(indexManager, namespace, timestamp, properties);
            
        }

        public void create() {
            
            super.create();
            
            if (getIndex() == null) {

                getIndexManager().registerIndex(
                        new IndexMetadata(getNamespace() + indexName, UUID
                                .randomUUID()));

                log.info("Created index.");

                getIndex();
                
            }
            
        }

        public void destroy() {
            
            super.destroy();
            
            if (getIndex() != null) {

                getIndexManager().dropIndex(getNamespace() + indexName);

                log.info("Dropped index.");
                
            }

        }

        private IIndex getIndex() {

            if (ndx == null) {

                ndx = getIndex(getNamespace() + indexName);

            }

            if (ndx == null) {

                log.info("Index not found.");

            }

            return ndx;

        }
        
        @Override
        public String getFQN(IKeyOrder keyOrder) {
            return null;
        }

        public long delete(IChunkedOrderedIterator itr) {
            return 0;
        }

        public long insert(IChunkedOrderedIterator itr) {
            return 0;
        }

        public Set getIndexNames() {
            return null;
        }

        public IKeyOrder getPrimaryKeyOrder() {
            return null;
        }

        public Iterator getKeyOrders() {
            return null;
        }

        public IKeyOrder getKeyOrder(IPredicate p) {
            return null;
        }
        
        public Object newElement(List a, IBindingSet bindingSet) {
            return null;
        }
    
        public Class<ISPO> getElementClass() {
            return null;
        }

    } // class MockRelation
    
}
